 
package com.wsc.crawler;

import static com.wsc.crawler.grabber.Grabber.isStopped;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.InetSocketAddress;
import java.net.URL;
import java.nio.charset.Charset;
import java.security.KeyStore;
import java.util.List;
import java.util.Locale;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;

import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.HttpStatus;
import org.apache.http.MethodNotSupportedException;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.nio.DefaultHttpServerIODispatch;
import org.apache.http.impl.nio.DefaultNHttpServerConnection;
import org.apache.http.impl.nio.DefaultNHttpServerConnectionFactory;
import org.apache.http.impl.nio.SSLNHttpServerConnectionFactory;
import org.apache.http.impl.nio.reactor.DefaultListeningIOReactor;
import org.apache.http.nio.NHttpConnectionFactory;
import org.apache.http.nio.NHttpServerConnection;
import org.apache.http.nio.entity.NStringEntity;
import org.apache.http.nio.protocol.BasicAsyncRequestConsumer;
import org.apache.http.nio.protocol.BasicAsyncResponseProducer;
import org.apache.http.nio.protocol.HttpAsyncExchange;
import org.apache.http.nio.protocol.HttpAsyncRequestConsumer;
import org.apache.http.nio.protocol.HttpAsyncRequestHandler;
import org.apache.http.nio.protocol.HttpAsyncRequestHandlerRegistry;
import org.apache.http.nio.protocol.HttpAsyncService;
import org.apache.http.nio.reactor.IOEventDispatch;
import org.apache.http.nio.reactor.ListeningIOReactor;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpParams;
import org.apache.http.params.SyncBasicHttpParams;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpProcessor;
import org.apache.http.protocol.ImmutableHttpProcessor;
import org.apache.http.protocol.ResponseConnControl;
import org.apache.http.protocol.ResponseContent;
import org.apache.http.protocol.ResponseDate;
import org.apache.http.protocol.ResponseServer;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;

import com.wsc.crawler.init.CrawlerConfig;

/*
 * This is Implementation of Asynchronous HTTP Server, which can handle multiple connections in parallel.
 * 
 */

public class AsyncHTTPServer {

	public static Logger log=Logger.getLogger(AsyncHTTPServer.class.getName());
	private static  CrawlerConfig config ;
	
	
	public AsyncHTTPServer(){
		
		config = new CrawlerConfig();
		config.loadDefaultXmlFile();
	 
 	}
	
	
	public static void main(String...s){
		
		try {
			(new AsyncHTTPServer()).start();
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}
	
    public void start() throws Exception { 
 
        /*
         * Request Listen Port for Server.
         * Put this port in Serverconfig.xml file.
         * While intializing server get it from config file, and make this as listen port.
         * 
         *  And put secondary port field also, because if another server listening on same port, then 
         *  port binding exception will arise. To avoid this the intializer will intialize the server 
         *  with secodary listen port.
         */
    	
        int port =config.getHeartBeat_Host().getPort();

        // HTTP parameters for the server
        HttpParams params = new SyncBasicHttpParams();
        params
            .setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 5000)
            .setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024)
            .setParameter(CoreProtocolPNames.ORIGIN_SERVER, "HttpTest/1.1");
     
        // Create HTTP protocol processing chain
       
        HttpProcessor httpproc = new ImmutableHttpProcessor(new HttpResponseInterceptor[] {
                // Use standard server-side protocol interceptors
                new ResponseDate(),
                new ResponseServer(),
                new ResponseContent(),
                new ResponseConnControl()
        });
        // Create request handler registry
        HttpAsyncRequestHandlerRegistry reqistry = new HttpAsyncRequestHandlerRegistry();
        // Register the default handler for all URIs
        reqistry.register("*", new HttpRequestHandler());
        // Create server-side HTTP protocol handler
        HttpAsyncService protocolHandler = new HttpAsyncService(
                httpproc, new DefaultConnectionReuseStrategy(), reqistry, params) {

            @Override
            public void connected(final NHttpServerConnection conn) {
                System.out.println(conn + ": connection open");
                super.connected(conn);
            }

            @Override
            public void closed(final NHttpServerConnection conn) {
                System.out.println(conn + ": connection closed");
                super.closed(conn);
            }

        };
        
        // Create HTTP connection factory
        NHttpConnectionFactory<DefaultNHttpServerConnection> connFactory;
        if (port == 8443) {
            // Initialize SSL context
            ClassLoader cl = AsyncHTTPServer.class.getClassLoader();
            URL url = cl.getResource("my.keystore");
            if (url == null) {
                System.out.println("Keystore not found");
                System.exit(1);
            }
            KeyStore keystore  = KeyStore.getInstance("jks");
            keystore.load(url.openStream(), "secret".toCharArray());
            KeyManagerFactory kmfactory = KeyManagerFactory.getInstance(
                    KeyManagerFactory.getDefaultAlgorithm());
            kmfactory.init(keystore, "secret".toCharArray());
            KeyManager[] keymanagers = kmfactory.getKeyManagers();
            SSLContext sslcontext = SSLContext.getInstance("TLS");
            sslcontext.init(keymanagers, null, null);
            connFactory = new SSLNHttpServerConnectionFactory(sslcontext, null, params);
        } else {
            connFactory = new DefaultNHttpServerConnectionFactory(params);
        }
        // Create server-side I/O event dispatch
        IOEventDispatch ioEventDispatch = new DefaultHttpServerIODispatch(protocolHandler, connFactory);
        // Create server-side I/O reactor
        ListeningIOReactor ioReactor = new DefaultListeningIOReactor();
        try {
            // Listen of the given port
            ioReactor.listen(new InetSocketAddress(port));
            // Ready to go!
            ioReactor.execute(ioEventDispatch);
        } catch (InterruptedIOException ex) {
            System.err.println("Interrupted");
        } catch (IOException e) {
            System.err.println("I/O error: " + e.getMessage());
        }
        System.out.println("Shutdown");
    }

    static class HttpRequestHandler implements HttpAsyncRequestHandler<HttpRequest> {


        public HttpRequestHandler() {
            super();
        }

        public HttpAsyncRequestConsumer<HttpRequest> processRequest(
                final HttpRequest request,
                final HttpContext context) {
            // Buffer request content in memory for simplicity
            return new BasicAsyncRequestConsumer();
        }

        public void handle(
                final HttpRequest request,
                final HttpAsyncExchange httpexchange,
                final HttpContext context) throws HttpException, IOException {
            HttpResponse response = httpexchange.getResponse();
            handleInternal(request, response, context);
            httpexchange.submitResponse(new BasicAsyncResponseProducer(response));
        }

        private void handleInternal(
                final HttpRequest request,
                final HttpResponse response,
                final HttpContext context) throws HttpException, IOException {

            String method = request.getRequestLine().getMethod().toUpperCase(Locale.ENGLISH);
            if (!method.equals("GET") && !method.equals("HEAD") && !method.equals("POST")) {
                throw new MethodNotSupportedException(method + " method not supported");
            }

            
            		// If Client post any data in its body part then this block will
         			// execute.
         			if (request instanceof HttpEntityEnclosingRequest) {
 
          				// get entity from request.
         				HttpEntity entity = ((HttpEntityEnclosingRequest) request)
         						.getEntity();

         				// Convert it into byteArray.
         				String xml = EntityUtils.toString(entity);

         				System.out.println("Incoming entity content (bytes): "
         						+ xml.length());
         	
         				// writing xml to a file
         	     	/*		
         				File f=new File("postedURLS.xml");
         				FileOutputStream st=new FileOutputStream(f);
         				DataOutputStream d=new DataOutputStream(st);
         				d.writeBytes(xml);
         				d.close();
         				st.close();
         			*/
         			}

         			// Get the index of the character in requested path in HttpRequest.
         			int index = request.getRequestLine().getUri().indexOf('?');

         			// If index is greater than 0 means that requested path has atleat a
         			// key-value pair.
         			if (index > 0) {

         				// Get the Query part of the requested path in HttpRequest.
         				String querypart = request.getRequestLine().getUri()
         						.substring(index + 1);

         				// Construct a KEY-VALUE pair List from querypart string.
         				// default delimator is &.

         				List<NameValuePair> params = URLEncodedUtils.parse(querypart,
         						Charset.defaultCharset());

         				// check the list size.
         				if (!params.isEmpty()) {

         					// read key from NameValuePair object in List.
         					String name = params.get(0).getName();

         					// read value from NameValuePair object in List.
         					String value = params.get(0).getValue();

         				
         					// If parameter is opearation

         					if (CrawlerOperations.OPERATION.equals(name)) {

         						// If value is heartbeat
         						
         						if (CrawlerOperations.O_CRAWLER_HEARTBEAT.equals(value)) {

         							log.debug("Request recieved for operation "+value);
         							// set response status 200 OK
         							
         							response.setStatusCode(HttpStatus.SC_OK);
         						
         							// Construct a String Entity, with proper message.
         							NStringEntity entity = new NStringEntity("amfine");
         						
         							// And set it in Response.
         							
         							response.setEntity(entity);
         							log.debug("Request Served Successfully");
         							
         							
         						} else if(CrawlerOperations.O_CRAWLER_STOP.equals(value)){
         							log.info("Server Recieved a Stop Request...");
         							log.info("Trying to stop crawler...");
         							
         							// Setting isstopped to true.  
         							isStopped = true;
         							
         						} else if(CrawlerOperations.O_START_CRAWLER.equals(value)){
         						
         							//code to start crawler.
         							
         							log.info("Starting Crawler...");
         							
         							WSCrawler crawler =WSCrawler.getInstance();
         							
         							crawler.setConfig(config);
         							
         							// Init crawler
         							crawler.initCrawler();

         							// if everything fine start crawler.

         							crawler.startCrawler();

         						}
         						else {
         							
         							// IMPLEMENT OTHER OPERATION  REQUESTS VALUES HERE.
         							
         							
         							log.debug("Opeartion value (" + value
         									+ ") not yet Implemented");
         							response.setStatusCode(HttpStatus.SC_NOT_FOUND);

             						// Set Entity message notfound in HTML.
             						NStringEntity entity = new NStringEntity(
             								"<html><body><h1>Parameter value("
             										+value+ ") is not Defined</h1></body></html>",
             								ContentType.create("text/html", "UTF-8"));

             						// Set the Entity in Response meassage.
             						response.setEntity(entity); // edit : not mandatory.
         							
         						}

         					} else {

         						log.warn("Unsupported operation (" + name
         								+ ") found in request.");

         						// If operation parameter is not equals to
         						// CrawlerOperations.OPERATION then send 404 notfound
         						// message.
         				       
         						response.setStatusCode(HttpStatus.SC_NOT_FOUND);

         						// Set Entity message notfound in HTML.
         						NStringEntity entity = new NStringEntity(
         								"<html><body><h1>Parameter ("
         										+name+ ") is not Supported</h1></body></html>",
         								ContentType.create("text/html", "UTF-8"));

         						// Set the Entity in Response meassage.
         						response.setEntity(entity); // edit : not mandatory.

         					}

         				}

         			}

               }  

    }
}

